package com.skt.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.skt.domain.DTO.LoginDTO;
import com.skt.domain.DTO.TokenPairDTO;
import com.skt.domain.ENUM.AccountStatus;
import com.skt.domain.ENUM.UserRole;
import com.skt.domain.ENUM.UserRoleStatus;
import com.skt.domain.PO.User;
import com.skt.domain.VO.LogoutResultVO;
import com.skt.domain.VO.UserVO;
import com.skt.exception.AuthErrorCode;
import com.skt.exception.AuthException;
import com.skt.exception.ValidationErrorCode;
import com.skt.exception.ValidationException;
import com.skt.mapper.AuthMapper;
import com.skt.mapper.UserMapper;
import com.skt.security.LoginUser;
import com.skt.service.AuthService;
import com.skt.service.BloomFilterService;
import com.skt.utils.JwtUtil;
import com.skt.utils.RedisUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.security.PublicKey;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 认证服务实现类 - 提供用户注册、登录等核心认证功能
 *
 * 核心功能：
 * 1. 用户注册：参数校验、防重复注册、分布式锁防并发
 * 2. 用户登录：多级缓存查询、密码验证、令牌生成、风控检测
 * 3. 安全防护：IP限制、失败次数限制、设备指纹验证
 * 4. 缓存管理：用户信息缓存、登录状态缓存、令牌管理
 *
 * @author SKT
 * @version 1.0
 * @since 2024
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class AuthServiceImpl extends ServiceImpl<AuthMapper, User> implements AuthService {

    private static final Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);

    // ========== 依赖注入 ==========
    private final JwtUtil jwtUtil; // JWT令牌工具类
    private final RedisUtil redisUtil; // Redis缓存工具类
    private final UserMapper userMapper; // 用户数据访问层
    private final PasswordEncoder passwordEncoder; // 密码加密器
    private final BloomFilterService bloomFilterService; // 布隆过滤器服务
    private final RedissonClient redissonClient; // 分布式锁客户端，用于并发控制



    // ========== 业务常量定义 ==========
    private static final int MAX_LOGIN_ATTEMPTS_PER_IP = 10;      // 单个IP地址最大登录尝试次数（防止暴力破解）
    private static final int MAX_LOGIN_FAILURES_PER_USER = 5;     // 单个用户最大登录失败次数（账户锁定阈值）
    private static final int LOCK_DURATION_MINUTES = 30;          // 账户锁定持续时间（分钟）
    private static final int CACHE_EXPIRE_MINUTES = 30;           // 缓存数据过期时间（分钟）
    private static final int CACHE_EXPIRE_HOURS = 24;           // 缓存数据过期时间（小时）

    /**
     * 获取公钥的方法
     */
    private PublicKey getPublicKey() {
        return jwtUtil.getPublicKey();
    }

    /**
     * 用户注册业务逻辑
     * 完整注册流程：请求接收 → 参数校验 → 分布式锁获取 → 布隆过滤器检查 → 数据库查重验证 →
     * 用户实体创建 → 数据库插入 → 布隆过滤器更新 → 多级缓存更新 →
     * 数据转换脱敏 → 返回结果 → 分布式锁释放
     *
     * 安全特性：
     * - 分布式锁防止同一用户名并发注册
     * - 多维度用户存在性检查（用户名、邮箱、手机号）
     * - 密码自动加密存储
     * - 注册后自动缓存用户信息
     *
     * @param loginDTO 注册请求参数，包含用户名、密码、邮箱、手机号等
     * @return UserVO 用户视图对象，不包含敏感信息如密码
     * @throws AuthException 认证相关异常（如用户已存在、操作频繁等）
     * @throws ValidationException 参数校验异常（如参数为空、格式错误等）
     */
    @Override
    public UserVO register(LoginDTO loginDTO) {
        logger.info("用户注册开始: username={}", loginDTO.getUsername());

        // 1. 参数基础校验（非空、格式等）
        validateRegisterParams(loginDTO);

        // 2. 分布式锁防并发注册（防止同一用户名被同时注册）
        String lockKey = "register_lock:" + loginDTO.getUsername();
        String requestId = UUID.randomUUID().toString();
        boolean lockAcquired = false;

        try {
            // 尝试获取分布式锁，超时时间3秒（防止死锁）
            lockAcquired = redisUtil.tryLock(lockKey, requestId, 3, TimeUnit.SECONDS);
            if (!lockAcquired) {
                logger.warn("注册操作频繁: username={}", loginDTO.getUsername());
                throw AuthException.of(AuthErrorCode.REGISTER_OPERATION_FREQUENT);
            }

            // 3. ✅ 新增：使用布隆过滤器快速检查用户是否存在（防止缓存穿透）
            if (checkUserExistsWithBloomFilter(loginDTO)) {
                logger.warn("用户已存在: username={}", loginDTO.getUsername());
                throw AuthException.of(AuthErrorCode.USERNAME_EXISTS);
            }

            // 4. 创建新用户并保存到数据库
            User newUser = createNewUser(loginDTO);
            userMapper.insert(newUser);

            // 5. ✅ 新增：注册成功后更新布隆过滤器
            updateBloomFilterAfterRegister(newUser);

            // 6. 更新相关缓存（用户信息缓存、存在性标记缓存）
            updateUserCacheAfterRegister(newUser);

            // 7. 转换为前端可用的VO对象（脱敏敏感信息）
            UserVO userVO = convertToUserVo(newUser);

            logger.info("用户注册成功: userId={}, username={}", newUser.getId(), newUser.getUsername());
            return userVO;
        } finally {
            // 确保释放分布式锁（避免资源泄漏）
            if (lockAcquired) {
                redisUtil.releaseLock(lockKey, requestId);
            }
        }
    }

    /**
     * 用户登录业务逻辑
     * 完整登录流程：请求接收 → 参数校验 → 风控检查 → 分布式锁获取 → 布隆过滤器检查 →
     * 多级用户查询 → 密码安全验证 → 令牌生成 → 缓存状态同步 →
     * 登录后处理 → 数据转换返回 → 资源清理监控
     *
     * 安全特性：
     * - IP频率限制（防止暴力破解）
     * - 用户失败次数限制（账户自动锁定）
     * - 设备变更检测（安全审计）
     * - 分布式锁防并发攻击
     * - 多级缓存查询优化性能
     *
     * @param loginDTO 登录请求参数，包含用户名和密码
     * @return UserVO 包含用户基本信息和访问令牌
     * @throws AuthException 认证相关异常（如密码错误、账户锁定等）
     */
    @Override
    public UserVO login(LoginDTO loginDTO) {
        // 获取真实客户端IP（用于风控和审计）
        String clientIP = getClientIP();
        logger.info("用户登录请求: username={}, ip={}", loginDTO.getUsername(), clientIP);

        // 1. 基础参数校验和智能风控拦截
        validateLoginParams(loginDTO);
        riskControlCheck(loginDTO, clientIP);

        // 2. 分布式锁防并发登录攻击（防止密码暴力破解）
        String lockKey = "login_lock:" + loginDTO.getUsername();
        String requestId = UUID.randomUUID().toString();
        boolean lockAcquired = false;

        try {
            lockAcquired = redisUtil.tryLock(lockKey, requestId, 3, TimeUnit.SECONDS);
            if (!lockAcquired) {
                logger.warn("登录操作频繁: username={}, ip={}", loginDTO.getUsername(), clientIP);
                throw AuthException.of(AuthErrorCode.LOGIN_OPERATION_FREQUENT);
            }

            // 3. ✅ 优化：使用布隆过滤器增强的多级用户信息查询
            User user = getUserInfoWithBloomFilter(loginDTO);

            // 4. 安全密码验证（含失败计数和账户锁定逻辑）
            validatePassword(loginDTO.getPassword(), user.getPassword(), loginDTO.getUsername(), clientIP);

            // 5. 生成访问令牌和刷新令牌
            String accessToken = generateAccessToken(user);
            String refreshToken = generateRefreshToken(user);

            // 6. 登录后缓存同步（用户信息、登录状态、令牌映射）
            syncUserCacheAfterLogin(user, accessToken, clientIP);

            // 7. 登录后处理（更新最后登录时间、设备指纹等）
            processAfterLogin(user, clientIP);

            // 8. 构建响应数据（脱敏敏感信息）
            UserVO userVO = buildLoginUserVO(user, accessToken, refreshToken);

            logger.info("用户登录成功: userId={}, username={}", user.getId(), user.getUsername());
            return userVO;

        } catch (Exception e) {
            // 登录失败日志记录（用于安全审计）
            logger.warn("用户登录失败: username={}, ip={}, error={}",
                    loginDTO.getUsername(), clientIP, e.getMessage());
            throw e;
        } finally {
            // 9. 资源清理和监控记录（确保资源释放）
            if (lockAcquired) {
                redisUtil.releaseLock(lockKey, requestId);
            }
            recordLoginMetrics(loginDTO.getUsername(), clientIP);
        }
    }

    /**
     * 用户登出业务逻辑
     * 完整登出流程：请求接收 → 安全上下文验证 → 用户身份提取 → 令牌获取 → 分布式锁获取 →
     * 用户信息查询 → 客户端IP获取 → 令牌失效处理 → 多级缓存清理 → 安全上下文清理 →
     * 审计日志记录 → 结果数据构建 → 分布式锁释放 → 返回登出结果
     *
     * 安全特性：
     * - 令牌立即失效，防止继续使用
     * - 分布式锁防并发登出
     * - 支持单设备/全设备登出
     * - 完整审计日志记录
     *
     * @param logoutAllDevices 是否登出所有设备
     * @return LogoutResultVO 登出结果
     */
    @Override
    public LogoutResultVO logout(Boolean logoutAllDevices) {
        logger.info("用户登出开始: logoutAllDevices={}", logoutAllDevices);

        // 1. 令牌提取与验证
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            throw AuthException.of(AuthErrorCode.TOKEN_INVALID);
        }

        // 2. 用户身份确认
        LoginUser loginUser = extractLoginUser(authentication);
        Long userId = loginUser.getUserId();
        String token = getCurrentToken(); // 获取当前令牌

        // 3. 分布式锁防并发登出
        String lockKey = "logout_lock:" + userId;
        String requestId = UUID.randomUUID().toString();
        boolean lockAcquired = false;

        try {
            lockAcquired = redisUtil.tryLock(lockKey, requestId, 3, TimeUnit.SECONDS);
            if (!lockAcquired) {
                logger.warn("登出操作频繁: userId={}", userId);
                throw AuthException.of(AuthErrorCode.LOGOUT_OPERATION_FREQUENT);
            }

            // 4. 获取用户信息和客户端IP
            User user = getUserById(userId);
            String clientIP = getClientIP();

            // 5. 执行登出逻辑
            LogoutResultVO result = executeLogout(user, token, logoutAllDevices, clientIP);

            logger.info("用户登出成功: userId={}, username={}, logoutAllDevices={}",
                    userId, user.getUsername(), logoutAllDevices);
            return result;

        } finally {
            // 确保释放分布式锁
            if (lockAcquired) {
                redisUtil.releaseLock(lockKey, requestId);
            }
        }
    }

    /**
     * 令牌刷新接口
     * 使用刷新令牌获取新的访问令牌，延长会话有效期
     * 包含完整的令牌验证、安全检查和缓存更新流程
     *
     * @param refreshToken 刷新令牌字符串
     * @return UserVO 包含新令牌的用户视图对象
     * @throws AuthException 令牌无效、过期或验证失败时抛出
     */
    @Override
    public UserVO refreshToken(String refreshToken) {
        logger.info("开始刷新令牌: refreshToken={}",
                refreshToken != null ? refreshToken.substring(0, 10) + "..." : "null");

        // 1. 刷新令牌基础验证(空值、过期时间、黑名单校验)
        Claims claims = validateRefreshToken(refreshToken);

        // 2. 从令牌声明中提取用户名，并查询对应用户信息
        String username = extractUsernameFromClaims(claims);
        User user = getUserByUsername(username);

        // 3. 验证用户状态（是否启用、是否锁定等）
        validateUserStatus(user);

        // 4. 分布式锁防并发刷新（同一用户同时刷新令牌）
        String refreshLockKey = "refresh_lock:" + username;
        RLock lock = redissonClient.getLock(refreshLockKey);
        boolean lockAcquired = false;

        try {
            // 尝试获取分布式锁，等待3秒，锁持有30秒，时间单位秒
            lockAcquired = lock.tryLock(3, 30, TimeUnit.SECONDS);
            if (!lockAcquired) {
                logger.warn("刷新令牌操作频繁: username={}", username);
                throw AuthException.of(AuthErrorCode.REFRESH_OPERATION_FREQUENT);
            }

            // 5. 验证刷新令牌是否在有效会话中
            validateUserSession(user.getId(), refreshToken);

            // 6. 设备一致性检查（防止令牌被盗用）
            validateDeviceConsistency(user.getUsername());

            // 7. 使旧令牌失效（加入黑名单）
            invalidateOldTokens(user.getId(), refreshToken);

            // 8. 生成新的令牌对（访问令牌 + 刷新令牌）
            TokenPairDTO newTokens = generateNewTokenPair(user);

            // 9. 同步更新缓存中的令牌状态
            syncCacheAfterTokenRefresh(user, newTokens, refreshToken);

            // 10. 记录令牌刷新审计日志
            recordTokenRefreshAudit(user, newTokens);

            // 11. 构建并返回响应结果
            return buildRefreshTokenResponse(user, newTokens);

        } catch (InterruptedException e) {
            // 线程中断处理
            Thread.currentThread().interrupt();
            logger.error("令牌刷新操作被中断: username={}", username, e);
            throw AuthException.of(AuthErrorCode.SYSTEM_ERROR);
        } finally {
            // 确保释放分布式锁
            if (lockAcquired) {
                lock.unlock();
                logger.debug("释放刷新令牌分布式锁: username={}", username);
            }
        }
    }


    /**
     * 构建刷新令牌响应
     *
     * @param user 用户实体对象
     * @param newTokens 新的令牌对
     * @return UserVO 用户视图对象
     */
    private UserVO buildRefreshTokenResponse(User user, TokenPairDTO newTokens) {
        try {
            UserVO userVO = new UserVO();

            // 设置用户基本信息
            userVO.setId(user.getId());
            userVO.setUsername(user.getUsername());
            userVO.setRealName(user.getRealName());
            userVO.setPhone(user.getPhone());
            userVO.setEmail(user.getEmail());

            // 设置角色和状态信息
            if (user.getRole() != null) {
                userVO.setRole(user.getRole().getValue().byteValue());
            }
            if (user.getAccountStatus() != null) {
                userVO.setStatus(user.getAccountStatus().getValue().byteValue());
            }

            // 设置时间信息
            userVO.setCreatedAt(user.getCreatedAt());
            userVO.setUpdatedAt(user.getUpdatedAt());

            // 设置新的访问令牌
            userVO.setToken(newTokens.getAccessToken());

            logger.info("令牌刷新成功: userId={}, username={}", user.getId(), user.getUsername());
            return userVO;

        } catch (Exception e) {
            logger.error("构建响应结果失败: userId={}, error={}", user.getId(), e.getMessage());
            throw AuthException.of(AuthErrorCode.SYSTEM_ERROR);
        }
    }

    /**
     * 记录令牌刷新审计日志
     *
     * @param user 用户实体对象
     * @param newTokens 新的令牌对
     */
    private void recordTokenRefreshAudit(User user, TokenPairDTO newTokens) {
        try {
            Map<String, Object> auditLog = new HashMap<>();
            auditLog.put("userId", user.getId());
            auditLog.put("username", user.getUsername());
            auditLog.put("refreshTime", System.currentTimeMillis());
            auditLog.put("newAccessTokenPrefix", newTokens.getAccessToken().substring(0, 10) + "...");
            auditLog.put("newRefreshTokenPrefix", newTokens.getRefreshToken().substring(0, 10) + "...");
            auditLog.put("expireTime", newTokens.getExpireTime());
            auditLog.put("userAgent", getUserAgent());
            auditLog.put("clientIP", getClientIP());

            String auditKey = "refresh_token_audit:" + user.getId() + ":" + System.currentTimeMillis();
            redisUtil.set(auditKey, auditLog, 7, TimeUnit.DAYS);

            logger.info("刷新令牌审计日志记录: userId={}, username={}", user.getId(), user.getUsername());
        } catch (Exception e) {
            logger.warn("记录刷新令牌审计日志失败: {}", e.getMessage());
        }
    }

    /**
     * 同步缓存状态
     * 在令牌刷新后更新缓存中的令牌信息
     *
     * @param user 用户实体对象
     * @param newTokens 新的令牌对
     * @param oldRefreshToken 旧的刷新令牌
     */
    private void syncCacheAfterTokenRefresh(User user, TokenPairDTO newTokens, String oldRefreshToken) {
        try {
            Long userId = user.getId();
            String newRefreshToken = newTokens.getRefreshToken();
            String username = user.getUsername();

            // 1. 删除旧令牌的映射关系
            redisUtil.hDelete("user_tokens:" + userId, oldRefreshToken);
            redisUtil.delete("token_user:" + oldRefreshToken);

            // 2. 准备新令牌的缓存信息
            Map<String, Object> tokenInfo = new HashMap<>();
            tokenInfo.put("accessToken", newTokens.getAccessToken());
            tokenInfo.put("createTime", System.currentTimeMillis());
            tokenInfo.put("device", getCurrentDevice());
            tokenInfo.put("userAgent", getUserAgent());

            // 3. 计算令牌的剩余有效时间
            long accessTokenExpireTime = newTokens.getAccessTokenExpiresIn();
            long refreshTokenExpireTime = newTokens.getRefreshTokenExpiresIn();

            long accessTokenTTL = Math.max(0, accessTokenExpireTime - System.currentTimeMillis());
            long refreshTokenTTL = Math.max(0, refreshTokenExpireTime - System.currentTimeMillis());

            // 4. 设置用户令牌映射（与刷新令牌同生命周期）
            redisUtil.hPut("user_tokens:" + userId, newRefreshToken, tokenInfo);
            redisUtil.expire("user_tokens:" + userId, refreshTokenTTL, TimeUnit.MILLISECONDS);

            // 5. 设置令牌-用户映射（快速查找令牌对应的用户）
            redisUtil.set("token_user:" + newRefreshToken, userId, refreshTokenTTL, TimeUnit.MILLISECONDS);

            // 6. 设置用户登录状态
            redisUtil.set("user_login_status:" + userId, "online", refreshTokenTTL, TimeUnit.MILLISECONDS);

            // 7. 设置设备信息
            String deviceKey = "login_device:" + username;
            redisUtil.set(deviceKey, getCurrentDevice(), refreshTokenTTL, TimeUnit.MILLISECONDS);

            logger.debug("缓存同步完成: userId={}, accessTokenTTL={}分钟, refreshTokenTTL={}天",
                    userId,
                    accessTokenTTL / (60 * 1000),
                    refreshTokenTTL / (24 * 60 * 60 * 1000));

        } catch (Exception e) {
            logger.error("同步缓存状态失败: userId={}, error={}", user.getId(), e.getMessage());
        }
    }

    /**
     * 生成新的令牌对
     * 创建新的访问令牌和刷新令牌
     *
     * @param user 用户实体对象
     * @return TokenPairDTO 令牌对数据传输对象
     * @throws AuthException 令牌生成失败时抛出
     */
    private TokenPairDTO generateNewTokenPair(User user) {
        try {
            // 1. 生成新的访问令牌（短期令牌，通常30分钟）
            String newAccessToken = jwtUtil.generateAccessToken(user.getUsername());

            // 2. 生成新的刷新令牌（长期令牌，通常7天）
            String newRefreshToken = jwtUtil.generateRefreshToken(user.getUsername());

            // 3. 解析令牌获取过期时间信息
            Claims accessTokenClaims = jwtUtil.parseClaims(newAccessToken);
            Claims refreshTokenClaims = jwtUtil.parseClaims(newRefreshToken);

            Long accessTokenExpireTime = accessTokenClaims.getExpiration().getTime();
            Long refreshTokenExpireTime = refreshTokenClaims.getExpiration().getTime();

            // 4. 构建令牌对DTO
            TokenPairDTO tokenPair = new TokenPairDTO();
            tokenPair.setAccessToken(newAccessToken);
            tokenPair.setRefreshToken(newRefreshToken);
            tokenPair.setAccessTokenExpiresIn(accessTokenExpireTime);
            tokenPair.setRefreshTokenExpiresIn(refreshTokenExpireTime);
            tokenPair.setExpireTime(accessTokenExpireTime); // 向后兼容

            logger.debug("生成新令牌对成功: userId={}, accessExpires={}, refreshExpires={}",
                    user.getId(),
                    new Date(accessTokenExpireTime),
                    new Date(refreshTokenExpireTime));

            return tokenPair;
        } catch (Exception e) {
            logger.error("生成令牌对失败: userId={}, error={}", user.getId(), e.getMessage());
            throw AuthException.of(AuthErrorCode.TOKEN_GENERATE_FAILED);
        }
    }

    /**
     * 使旧令牌失效
     * 将使用过的刷新令牌加入黑名单，防止重复使用
     *
     * @param userId 用户ID
     * @param refreshToken 刷新令牌
     */
    private void invalidateOldTokens(Long userId, String refreshToken) {
        try {
            // 1. 计算令牌剩余有效时间
            long ttl = getTokenRemainingTime(refreshToken);
            if (ttl > 0) {
                // 2. 将令牌加入黑名单（在剩余时间内有效）
                String blacklistKey = "refresh_token_blacklist:" + refreshToken;
                redisUtil.set(blacklistKey, "1", ttl, TimeUnit.MILLISECONDS);
            }

            // 3. 清理令牌映射关系
            redisUtil.delete("token_user:" + refreshToken);
            redisUtil.hDelete("user_tokens:" + userId, refreshToken);

            logger.debug("旧令牌失效处理完成: userId={}", userId);
        } catch (Exception e) {
            logger.warn("失效旧令牌失败: userId={}, error={}", userId, e.getMessage());
        }
    }

    /**
     * 验证设备一致性
     * 检查当前设备与上次登录设备是否一致，防止令牌盗用
     *
     * @param username 用户名
     */
    private void validateDeviceConsistency(String username) {
        try {
            String deviceKey = "login_device:" + username;
            String currentDevice = getCurrentDevice();
            String lastDevice = (String) redisUtil.get(deviceKey);

            if (lastDevice != null && !lastDevice.equals(currentDevice)) {
                logger.warn("设备变更检测: username={}, lastDevice={}, currentDevice={}",
                        username, lastDevice, currentDevice);
                // 可以根据安全策略决定是否抛出异常或只是记录警告
            }
        } catch (Exception e) {
            logger.warn("设备一致性检查失败: username={}, error={}", username, e.getMessage());
        }
    }

    /**
     * 获取当前设备指纹
     * 基于User-Agent等请求头生成设备唯一标识
     *
     * @return 设备指纹字符串
     */
    private String getCurrentDevice() {
        try {
            HttpServletRequest request = getCurrentRequest();
            String userAgent = request.getHeader("User-Agent");
            String accept = request.getHeader("Accept");
            String acceptLanguage = request.getHeader("Accept-Language");
            String acceptEncoding = request.getHeader("Accept-Encoding");

            String deviceFingerprint = userAgent + "|" + accept + "|" + acceptLanguage + "|" + acceptEncoding;
            return String.valueOf(deviceFingerprint.hashCode());
        } catch (Exception e) {
            logger.warn("获取设备指纹失败", e);
            return "unknown_device";
        }
    }

    /**
     * 获取当前HTTP请求对象
     *
     * @return HttpServletRequest对象
     */
    private HttpServletRequest getCurrentRequest() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
    }

    /**
     * 验证用户会话有效性
     * 检查刷新令牌是否在用户的活跃会话中
     *
     * @param userId 用户ID
     * @param refreshToken 刷新令牌
     * @throws AuthException 令牌不在会话中时抛出
     */
    private void validateUserSession(Long userId, String refreshToken) {
        try {
            String userTokensKey = "user_tokens:" + userId;
            Boolean tokenExists = redisUtil.hHasKey(userTokensKey, refreshToken);

            if (Boolean.FALSE.equals(tokenExists)) {
                logger.warn("刷新令牌不在有效会话中: userId={}", userId);
                throw AuthException.of(AuthErrorCode.REFRESH_TOKEN_NOT_IN_SESSION);
            }

            logger.debug("用户会话验证通过: userId={}", userId);
        } catch (AuthException e) {
            // 直接抛出业务异常
            throw e;
        } catch (Exception e) {
            logger.error("验证用户会话失败: userId={}, error={}", userId, e.getMessage());
            throw AuthException.of(AuthErrorCode.SYSTEM_ERROR);
        }
    }

    /**
     * 验证用户账户状态
     * 检查用户是否被禁用、锁定等异常状态
     *
     * @param user 用户实体对象
     * @throws AuthException 账户状态异常时抛出
     */
    private void validateUserStatus(User user) {
        if (user.getAccountStatus() != AccountStatus.ENABLED) {
            logger.warn("用户账户状态异常: username={}, status={}",
                    user.getUsername(), user.getAccountStatus());
            throw AuthException.of(AuthErrorCode.ACCOUNT_DISABLED);
        }
    }

    /**
     * 根据用户名查询用户信息
     *
     * @param username 用户名
     * @return 用户实体对象
     * @throws AuthException 用户不存在或查询失败时抛出
     */
    private User getUserByUsername(String username) {
        try {
            User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
            if (user == null) {
                logger.warn("用户不存在: username={}", username);
                throw AuthException.of(AuthErrorCode.USER_NOT_EXIST);
            }
            return user;
        } catch (Exception e) {
            logger.error("查询用户信息失败: username={}", username, e);
            throw AuthException.of(AuthErrorCode.SYSTEM_ERROR);
        }
    }

    /**
     * 从令牌声明中提取用户名
     *
     * @param claims JWT声明对象
     * @return 用户名字符串
     * @throws AuthException 用户名为空时抛出
     */
    private String extractUsernameFromClaims(Claims claims) {
        try {
            String username = claims.getSubject();
            if (StringUtils.isBlank(username)) {
                logger.error("刷新令牌中用户名为空");
                throw AuthException.of(AuthErrorCode.REFRESH_TOKEN_INVALID);
            }
            return username;
        } catch (Exception e) {
            logger.error("从令牌提取用户名失败", e);
            throw AuthException.of(AuthErrorCode.REFRESH_TOKEN_INVALID);
        }
    }

    /**
     * 验证刷新令牌的有效性
     * 检查令牌格式、签名、过期时间、黑名单状态等
     *
     * @param refreshToken 刷新令牌字符串
     * @return Claims 令牌声明信息
     * @throws AuthException 令牌无效时抛出
     */
    private Claims validateRefreshToken(String refreshToken) {
        // 基础空值检查
        if (StringUtils.isBlank(refreshToken)) {
            throw AuthException.of(AuthErrorCode.REFRESH_TOKEN_MISSING);
        }

        try {
            // 1. 解析令牌声明
            Claims claims = jwtUtil.parseClaims(refreshToken);

            // 2. 检查令牌是否过期
            if (claims.getExpiration().before(new Date())) {
                throw AuthException.of(AuthErrorCode.REFRESH_TOKEN_EXPIRED);
            }

            // 3. 检查令牌是否被加入黑名单
            String blacklistKey = "refresh_token_blacklist:" + refreshToken;
            if (Boolean.TRUE.equals(redisUtil.hasKey(blacklistKey))) {
                throw AuthException.of(AuthErrorCode.REFRESH_TOKEN_REVOKED);
            }

            return claims;

        } catch (ExpiredJwtException e) {
            // 令牌过期异常
            logger.warn("刷新令牌已过期: {}", e.getMessage());
            throw AuthException.of(AuthErrorCode.REFRESH_TOKEN_EXPIRED);
        } catch (JwtException e) {
            // JWT解析异常（签名错误、格式错误等）
            logger.warn("刷新令牌解析失败: {}", e.getMessage());
            throw AuthException.of(AuthErrorCode.REFRESH_TOKEN_INVALID);
        } catch (Exception e) {
            // 其他系统异常
            logger.error("验证刷新令牌异常: {}", e.getMessage());
            throw AuthException.of(AuthErrorCode.SYSTEM_ERROR);
        }
    }


    /**
     * 提取登录用户信息
     */
    private LoginUser extractLoginUser(Authentication authentication) {
        Object principal = authentication.getPrincipal();
        if (principal instanceof LoginUser) {
            return (LoginUser) principal;
        }
        throw AuthException.of(AuthErrorCode.TOKEN_INVALID);
    }

    /**
     * 获取当前请求的令牌
     */
    private String getCurrentToken() {
        try {
            HttpServletRequest request = ((ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes()).getRequest();
            String authHeader = request.getHeader("Authorization");
            if (authHeader != null && authHeader.startsWith("Bearer ")) {
                return authHeader.substring(7);
            }
            throw AuthException.of(AuthErrorCode.TOKEN_INVALID);
        } catch (Exception e) {
            throw AuthException.of(AuthErrorCode.TOKEN_INVALID);
        }
    }

    /**
     * 执行登出核心逻辑
     */
    private LogoutResultVO executeLogout(User user, String token, Boolean logoutAllDevices, String clientIP) {
        Long userId = user.getId();
        String username = user.getUsername();

        try {
            // 1. 令牌失效处理
            invalidateTokens(userId, token, logoutAllDevices);

            // 2. 清理缓存数据
            clearUserCache(userId, username, logoutAllDevices);

            // 3. 清理安全上下文
            clearSecurityContext();

            // 4. 记录审计日志
            recordLogoutAudit(user, logoutAllDevices, clientIP);

            // 5. 构建返回结果
            return buildLogoutResult(user, logoutAllDevices, clientIP, "ACTIVE");

        } catch (Exception e) {
            logger.error("登出过程异常: userId={}, error={}", userId, e.getMessage());
            throw AuthException.of(AuthErrorCode.LOGOUT_FAILED);
        }
    }

    /**
     * 令牌失效处理
     */
    private void invalidateTokens(Long userId, String currentToken, Boolean logoutAllDevices) {
        if (Boolean.TRUE.equals(logoutAllDevices)) {
            // 全设备登出：失效所有令牌
            invalidateAllUserTokens(userId);
        } else {
            // 单设备登出：只失效当前令牌
            invalidateSingleToken(currentToken, userId);
        }
    }

    /**
     * 失效单个令牌
     */
    private void invalidateSingleToken(String token, Long userId) {
        try {
            // 1. 计算令牌剩余有效期
            long ttl = getTokenRemainingTime(token);
            if (ttl > 0) {
                // 2. 将令牌加入黑名单
                String blacklistKey = "token_blacklist:" + token;
                // 这里的时间单位设置要与令牌剩余时间的单位设置相同
                redisUtil.set(blacklistKey, "1", ttl, TimeUnit.MILLISECONDS);
            }

            // 3. 清理令牌映射关系
            redisUtil.delete("token_user:" + token);
            redisUtil.hDelete("user_tokens:" + userId, token);

            logger.debug("单设备令牌失效: userId={}, token={}", userId, token.substring(0, 10) + "...");
        } catch (Exception e) {
            logger.warn("失效单令牌失败: userId={}, error={}", userId, e.getMessage());
        }
    }

    /**
     * 失效用户所有令牌
     */
    private void invalidateAllUserTokens(Long userId) {
        try {
            // 1. 获取用户所有活跃令牌
            String userTokensKey = "user_tokens:" + userId;
            Map<Object, Object> tokenMap = redisUtil.hEntries(userTokensKey);

            if (tokenMap != null && !tokenMap.isEmpty()) {
                // 2. 批量加入黑名单
                for (Object tokenObj : tokenMap.keySet()) {
                    String token = (String) tokenObj;
                    invalidateSingleToken(token, userId);
                }
            }

            // 3. 清理用户所有令牌记录
            redisUtil.delete(userTokensKey);
            redisUtil.delete("user_sessions:" + userId);

            logger.info("全设备令牌失效: userId={}, tokensCount={}", userId,
                    tokenMap != null ? tokenMap.size() : 0);
        } catch (Exception e) {
            logger.error("失效用户所有令牌失败: userId={}, error={}", userId, e.getMessage());
            throw e;
        }
    }

    /**
     * 获取令牌剩余时间
     */
    private long getTokenRemainingTime(String token) {
        try {
            // 通过 jwtUtil 获取公钥
            Claims claims = jwtUtil.parseClaims(token);

            Date expiration = claims.getExpiration();
            return Math.max(0, expiration.getTime() - System.currentTimeMillis());
        } catch (Exception e) {
            logger.warn("获取令牌剩余时间失败，使用默认值: {}", e.getMessage());
            return 3600000L; // 默认1小时（毫秒）
        }
    }

    /**
     * 清理用户缓存数据
     */
    private void clearUserCache(Long userId, String username, Boolean logoutAllDevices) {
        try {
            if (Boolean.TRUE.equals(logoutAllDevices)) {
                // 全设备登出：清理所有会话相关缓存
                redisUtil.delete("user_login_status:" + userId);
                redisUtil.delete("user_devices:" + userId);
                redisUtil.delete("user_last_login:" + userId);
                redisUtil.delete("user_online_status:" + userId);
            } else {
                // 单设备登出：只清理当前设备会话
                String deviceFingerprint = getDeviceFingerprint();
                redisUtil.hDelete("user_devices:" + userId, deviceFingerprint);

                // 保留用户基本信息缓存，只清理会话状态
                redisUtil.delete("user_login_status:" + userId);
            }

            logger.debug("用户缓存清理完成: userId={}, logoutAllDevices={}", userId, logoutAllDevices);
        } catch (Exception e) {
            logger.warn("清理用户缓存失败: userId={}, error={}", userId, e.getMessage());
        }
    }

    /**
     * 清理安全上下文
     */
    private void clearSecurityContext() {
        SecurityContextHolder.clearContext();
    }

    /**
     * 记录登出审计日志
     */
    private void recordLogoutAudit(User user, Boolean logoutAllDevices, String clientIP) {
        try {
            Map<String, Object> auditLog = new HashMap<>();
            auditLog.put("userId", user.getId());
            auditLog.put("username", user.getUsername());
            auditLog.put("logoutTime", System.currentTimeMillis());
            auditLog.put("logoutType", "ACTIVE");
            auditLog.put("logoutAllDevices", logoutAllDevices);
            auditLog.put("clientIP", clientIP);
            auditLog.put("userAgent", getUserAgent());

            // 存储到Redis，可配置TTL
            String auditKey = "logout_audit:" + user.getId() + ":" + System.currentTimeMillis();
            redisUtil.set(auditKey, auditLog, 7, TimeUnit.DAYS);

            logger.info("登出审计记录: userId={}, username={}, ip={}, logoutAllDevices={}",
                    user.getId(), user.getUsername(), clientIP, logoutAllDevices);
        } catch (Exception e) {
            logger.warn("记录登出审计日志失败: {}", e.getMessage());
        }
    }

    /**
     * 构建登出结果
     */
    private LogoutResultVO buildLogoutResult(User user, Boolean logoutAllDevices, String clientIP, String logoutType) {
        LogoutResultVO result = new LogoutResultVO();
        result.setUserId(user.getId());
        result.setUsername(user.getUsername());
        result.setRealName(user.getRealName());
        result.setRole(user.getRole());
        result.setLogoutTime(LocalDateTime.now());
        result.setLogoutType(logoutType);
        result.setLogoutAllDevices(logoutAllDevices);
        result.setAccountStatus(user.getAccountStatus());
        result.setRoleStatus(user.getRoleStatus());
        result.setClientIp(clientIP);
        result.setMessage("登出成功");

        // 设置影响的会话数量
        try {
            if (Boolean.TRUE.equals(logoutAllDevices)) {
                String sessionsKey = "user_sessions:" + user.getId();
                Long sessionCount = redisUtil.hSize(sessionsKey);
                result.setAffectedSessionCount(sessionCount != null ? sessionCount.intValue() : 1);
            } else {
                result.setAffectedSessionCount(1);
            }
        } catch (Exception e) {
            result.setAffectedSessionCount(1);
            logger.warn("计算影响会话数失败: {}", e.getMessage());
        }

        return result;
    }

    /**
     * 获取用户代理信息
     */
    private String getUserAgent() {
        try {
            HttpServletRequest request = ((ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes()).getRequest();
            return request.getHeader("User-Agent");
        } catch (Exception e) {
            return "unknown";
        }
    }

    /**
     * 根据ID获取用户信息
     */
    private User getUserById(Long userId) {
        User user = userMapper.selectById(userId);
        if (user == null) {
            throw AuthException.of(AuthErrorCode.USER_NOT_EXIST);
        }
        return user;
    }


    /**
     * 注册成功后更新布隆过滤器 - 保持数据一致性
     * 当新用户注册成功时，将其用户名添加到布隆过滤器中
     *
     * 重要性：确保新注册的用户在后续登录时能够被正确识别
     * 容错处理：更新失败不影响主注册流程，记录日志即可
     *
     * @param newUser 新注册的用户
     */
    private void updateBloomFilterAfterRegister(User newUser) {
        try {
            bloomFilterService.put(newUser.getUsername());
            logger.debug("注册成功，更新布隆过滤器: username={}", newUser.getUsername());
        } catch (Exception e) {
            // 布隆过滤器更新失败不影响主流程，但需要记录日志
            logger.error("更新布隆过滤器失败（不影响注册）: username={}, error={}",
                    newUser.getUsername(), e.getMessage());
        }
    }

    /**
     * 使用布隆过滤器检查用户是否存在 - 高性能缓存穿透防护
     * 原理：布隆过滤器可以快速判断"用户一定不存在"，避免无效的数据库查询
     *
     * 工作流程：
     * 1. 先查布隆过滤器：如果返回false，用户一定不存在（快速返回）
     * 2. 布隆过滤器返回true：用户可能存在，继续详细检查
     * 3. 详细检查：查询数据库确认用户是否存在
     *
     * 优势：防止恶意用户查询不存在的用户名，保护数据库
     *
     * @param loginDTO 登录请求参数
     * @return true: 用户已存在 false: 用户不存在
     */
    private boolean checkUserExistsWithBloomFilter(LoginDTO loginDTO) {
        String username = loginDTO.getUsername();

        // 1. 使用布隆过滤器快速判断用户是否存在
        boolean mightExist = bloomFilterService.mightContain(username);

        if (!mightExist) {
            // 2. 布隆过滤器确认用户一定不存在，快速返回
            logger.debug("布隆过滤器确认用户不存在: username={}", username);
            return false;
        } else {
            // 3. 布隆过滤器认为用户可能存在，进行详细检查
            logger.debug("布隆过滤器认为用户可能存在，进行详细检查: username={}", username);
            return checkUserExistsInDatabase(loginDTO);
        }
    }

    /**
     * 使用布隆过滤器增强的用户信息查询 - 优化登录性能
     * 在原有缓存查询基础上增加布隆过滤器防护层
     *
     * 查询层级优化：
     * 1. L0: 布隆过滤器（纳秒级，防止缓存穿透）
     * 2. L1: Redis缓存（毫秒级，缓存命中）
     * 3. L2: MySQL数据库（数据库查询，兜底方案）
     *
     * @param loginDTO 登录请求
     * @return 用户信息
     * @throws AuthException 用户不存在时抛出异常
     */
    private User getUserInfoWithBloomFilter(LoginDTO loginDTO) {
        String username = loginDTO.getUsername();

        // 1. L0检查：使用布隆过滤器快速判断用户是否存在
        if (!bloomFilterService.mightContain(username)) {
            logger.warn("用户不存在（布隆过滤器确认）: username={}", username);
            throw AuthException.of(AuthErrorCode.USERNAME_PASSWORD_ERROR);
        }

        // 2. L1检查：查询Redis缓存（原有逻辑保持不变）
        User user = getUserFromCache(username);
        if (user != null) {
            return user;
        }

        // 3. L2检查：查询数据库（原有逻辑保持不变）
        user = getUserFromDatabase(loginDTO);
        if (user == null) {
            logger.warn("用户不存在: username={}", username);
            throw AuthException.of(AuthErrorCode.USERNAME_PASSWORD_ERROR);
        }

        // 4. 账户状态验证和缓存回写（原有逻辑保持不变）
        validateAccountStatus(user);
        cacheUserInfo(user);
        return user;
    }

    /**
     * 智能风控检测 - 多层安全防护
     * 检查IP频率限制、用户失败次数、设备指纹异常等安全风险
     *
     * 防护策略：
     * - IP级别：单个IP在5分钟内最多10次登录尝试
     * - 用户级别：单个用户在5分钟内最多5次失败尝试
     * - 设备级别：检测设备变更，记录安全日志
     *
     * @param loginDTO 登录请求数据
     * @param clientIP 客户端真实IP地址
     * @throws AuthException 当检测到安全风险时抛出相应异常
     */
    private void riskControlCheck(LoginDTO loginDTO, String clientIP) {
        String ipLimitKey = "login_ip_limit:" + clientIP;
        String userFailCountKey = "login_fail_count:" + loginDTO.getUsername();

        // IP频率限制检查：防止单个IP的暴力破解攻击
        Integer ipLoginCount = (Integer) redisUtil.get(ipLimitKey);
        if (ipLoginCount != null && ipLoginCount >= MAX_LOGIN_ATTEMPTS_PER_IP) {
            logger.warn("IP登录次数超限: ip={}, count={}", clientIP, ipLoginCount);
            throw AuthException.of(AuthErrorCode.LOGIN_OPERATION_FREQUENT);
        }

        // 用户失败次数检查：防止针对特定用户的密码猜测
        Integer userFailCount = (Integer) redisUtil.get(userFailCountKey);
        if (userFailCount != null && userFailCount >= MAX_LOGIN_FAILURES_PER_USER) {
            logger.warn("用户登录失败次数超限: username={}, count={}", loginDTO.getUsername(), userFailCount);
            throw AuthException.of(AuthErrorCode.ACCOUNT_LOCKED);
        }

        // 设备指纹验证：记录设备变更情况（用于安全审计，不阻断正常登录）
        checkDeviceFingerprint(loginDTO.getUsername(), clientIP);
    }

    /**
     * 检查设备指纹 - 安全审计功能
     * 通过User-Agent等请求头生成设备指纹，检测设备变更情况
     *
     * 实现原理：
     * 1. 从HTTP请求头提取设备特征（User-Agent、Accept等）
     * 2. 生成哈希值作为设备指纹
     * 3. 与上次登录设备对比，记录变更情况
     *
     * @param username 用户名
     * @param clientIP 客户端IP（用于日志记录）
     */
    private void checkDeviceFingerprint(String username, String clientIP) {
        String deviceKey = "login_device:" + username;
        String currentDevice = getDeviceFingerprint();
        String lastDevice = (String) redisUtil.get(deviceKey);

        // 检测到设备变更时记录安全日志（不阻断登录，用于审计）
        if (lastDevice != null && !lastDevice.equals(currentDevice)) {
            logger.warn("设备变更检测: username={}, ip={}, lastDevice={}, currentDevice={}",
                    username, clientIP, lastDevice, currentDevice);
        }
    }

    /**
     * 安全密码验证 - 核心安全逻辑
     * 验证用户输入密码与数据库存储密码是否匹配，包含完整的失败处理机制
     *
     * 安全机制：
     * - 密码匹配：使用BCrypt安全对比（防时序攻击）
     * - 失败计数：记录失败次数，达到阈值锁定账户
     * - IP记录：同时记录IP失败次数（双重防护）
     * - 成功清理：验证成功时清除所有失败计数
     *
     * @param inputPassword 用户输入的明文密码
     * @param encodedPassword 数据库存储的加密密码
     * @param username 用户名（用于失败计数）
     * @param clientIP 客户端IP（用于IP限制）
     * @throws AuthException 密码错误或账户锁定时抛出异常
     */
    private void validatePassword(String inputPassword, String encodedPassword, String username, String clientIP) {
        // 使用passwordEncoder安全对比密码（防时序攻击）
        if (!passwordEncoder.matches(inputPassword, encodedPassword)) {
            // 记录IP登录尝试（IP级别防护）
            recordLoginAttempt(clientIP);

            // 记录用户密码错误（用户级别防护）
            recordPasswordFailure(username, clientIP);

            // 抛出密码错误异常（不泄露具体错误信息）
            throw AuthException.of(AuthErrorCode.USERNAME_PASSWORD_ERROR);
        }

        // 密码验证成功，清除所有错误计数（重置风控状态）
        clearLoginFailCounters(username, clientIP);
    }

    /**
     * 记录IP登录尝试 - IP级别风控
     * 每次登录尝试（无论成功失败）都记录IP尝试次数
     *
     * @param clientIP 客户端IP地址
     */
    private void recordLoginAttempt(String clientIP) {
        String ipLimitKey = "login_ip_limit:" + clientIP;
        Integer ipLoginCount = (Integer) redisUtil.get(ipLimitKey);
        ipLoginCount = ipLoginCount == null ? 1 : ipLoginCount + 1;
        if (ipLoginCount == 1) {
            // 只有第一次设置5分钟过期时间，后续不重置
            redisUtil.expire(ipLimitKey, 5, TimeUnit.MINUTES);
        }
        redisUtil.set(ipLimitKey, ipLoginCount);

        // 检查是否需要封禁IP（达到失败阈值）
        if (ipLoginCount >= MAX_LOGIN_ATTEMPTS_PER_IP) {
            String ipBlockKey = "ip_lock:" + clientIP; // 修正Key命名
            redisUtil.set(ipBlockKey, "1", LOCK_DURATION_MINUTES, TimeUnit.MINUTES);
            logger.warn("IP地址已被封禁: ip={}, 封禁时长={}分钟", clientIP, LOCK_DURATION_MINUTES);
            throw AuthException.of(AuthErrorCode.IP_LOCKED); // 使用专门的IP封禁错误码
        }
    }

    /**
     * 记录密码错误 - 用户级别风控
     * 记录用户密码错误次数，达到阈值时自动锁定账户
     *
     * @param username 用户名
     * @param clientIP 客户端IP（用于日志记录）
     * @throws AuthException 当达到锁定阈值时抛出账户锁定异常
     */
    private void recordPasswordFailure(String username, String clientIP) {
        String userFailCountKey = "login_fail_count:" + username;
        Integer userFailCount = (Integer) redisUtil.get(userFailCountKey);
        userFailCount = userFailCount == null ? 1 : userFailCount + 1;
        if (userFailCount == 1) {
            redisUtil.expire(userFailCountKey, 5, TimeUnit.MINUTES);
        }
        redisUtil.set(userFailCountKey, userFailCount);

        // 检查是否需要锁定账户（达到失败阈值）
        if (userFailCount >= MAX_LOGIN_FAILURES_PER_USER) {
            String lockKey = "account_lock:" + username;
            redisUtil.set(lockKey, "1", LOCK_DURATION_MINUTES, TimeUnit.MINUTES);
            logger.warn("账户锁定: username={}, ip={}", username, clientIP);
            throw AuthException.of(AuthErrorCode.ACCOUNT_LOCKED);
        }
    }

    /**
     * 清除登录失败计数器 - 登录成功处理
     * 用户登录成功时清除所有相关的失败计数，重置风控状态
     *
     * @param username 用户名
     * @param clientIP 客户端IP
     */
    private void clearLoginFailCounters(String username, String clientIP) {
        String userFailCountKey = "login_fail_count:" + username;
        String ipLimitKey = "login_ip_limit:" + clientIP;

        redisUtil.delete(userFailCountKey);
        redisUtil.delete(ipLimitKey);

        logger.debug("登录成功，清除计数: username={}, ip={}", username, clientIP);
    }

    /**
     * 更新设备指纹 - 登录成功时调用
     * 登录成功后更新设备指纹，用于后续登录的设备验证
     *
     * @param username 用户名
     */
    private void updateDeviceFingerprint(String username) {
        String deviceKey = "login_device:" + username;
        String currentDevice = getDeviceFingerprint();
        redisUtil.set(deviceKey, currentDevice, 30, TimeUnit.DAYS);
    }

    // ========== 数据访问层方法 ==========

    /**
     * 从缓存查询用户信息 - L1缓存查询
     * 查询Redis缓存中的用户信息，缓存命中可大幅提升性能
     *
     * @param username 用户名（缓存Key）
     * @return User 用户实体对象，缓存未命中时返回null
     */
    private User getUserFromCache(String username) {
        try {
            String cacheKey = "user_info:" + username;
            return (User) redisUtil.get(cacheKey);
        } catch (Exception e) {
            logger.error("查询用户缓存失败: username={}", username, e);
            return null; // 缓存查询失败时降级到数据库查询
        }
    }

    /**
     * 从数据库查询用户信息 - L2存储查询
     * 支持用户名、邮箱、手机号三种登录方式的多条件查询
     *
     * @param loginDTO 登录请求（包含登录标识符）
     * @return User 用户实体对象，未找到时返回null
     */
    private User getUserFromDatabase(LoginDTO loginDTO) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        String loginIdentifier = loginDTO.getUsername();

        // 智能识别登录标识符类型（邮箱/手机号/用户名）
        if (loginIdentifier.contains("@")) {
            queryWrapper.eq(User::getEmail, loginIdentifier); // 邮箱登录
        } else if (loginIdentifier.matches("^1[3-9]\\d{9}$")) {
            queryWrapper.eq(User::getPhone, loginIdentifier);  // 手机号登录
        } else {
            queryWrapper.eq(User::getUsername, loginIdentifier); // 用户名登录
        }

        return userMapper.selectOne(queryWrapper);
    }

    /**
     * 账户状态验证 - 业务规则验证
     * 检查用户账户状态，确保账户可用（非禁用状态）
     *
     * @param user 用户实体对象
     * @throws AuthException 当账户被禁用时抛出异常
     */
    private void validateAccountStatus(User user) {
        if (AccountStatus.DISABLED.equals(user.getAccountStatus())) {
            throw AuthException.of(AuthErrorCode.ACCOUNT_DISABLED);
        }
    }

    // ========== JWT令牌相关方法 ==========

    /**
     * 生成访问令牌 - 短期令牌
     * 生成用于API访问的短期令牌（默认2小时有效期）
     *
     * @param user 用户实体对象
     * @return String JWT格式的访问令牌
     */
    private String generateAccessToken(User user) {
        String subject = user.getId() + ":" + user.getUsername();
        return jwtUtil.generateAccessToken(subject); // 使用短期有效期
    }

    /**
     * 生成刷新令牌 - 长期令牌
     * 生成用于刷新访问令牌的长期令牌（默认7天有效期）
     *
     * @param user 用户实体对象
     * @return String JWT格式的刷新令牌
     */
    private String generateRefreshToken(User user) {
        String subject = "refresh:" + user.getId() + ":" + System.currentTimeMillis();
        return jwtUtil.generateRefreshToken(subject); // 使用长期有效期
    }

    // ========== 缓存管理方法 ==========

    /**
     * 登录后缓存同步 - 多维度缓存更新
     * 登录成功后更新多个维度的缓存数据，确保数据一致性
     *
     * @param user 用户实体对象
     * @param accessToken 访问令牌
     * @param clientIP 客户端IP
     */
    private void syncUserCacheAfterLogin(User user, String accessToken, String clientIP) {
        // 缓存用户基本信息（用于快速查询）
        cacheUserInfo(user);

        // 缓存登录状态信息（用于会话管理）
        String loginStatusKey = "user_login_status:" + user.getId();
        Map<String, Object> loginStatus = new HashMap<>();
        loginStatus.put("loginTime", System.currentTimeMillis());
        loginStatus.put("clientIP", clientIP);
        loginStatus.put("accessToken", accessToken);

        redisUtil.set(loginStatusKey, loginStatus, CACHE_EXPIRE_MINUTES, TimeUnit.MINUTES);

        // 缓存令牌映射（用于令牌验证）
        String tokenKey = "user_token:" + user.getId();
        redisUtil.set(tokenKey, accessToken, 2, TimeUnit.HOURS);
    }

    /**
     * 缓存用户信息 - 基础信息缓存
     * 将用户信息缓存到Redis，提升后续查询性能
     *
     * @param user 用户实体对象
     */
    private void cacheUserInfo(User user) {
        String cacheKey = "user_info:" + user.getUsername();
        redisUtil.set(cacheKey, user, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
    }

    /**
     * 登录后处理 - 业务逻辑处理
     * 登录成功后执行的相关业务逻辑（非核心安全逻辑）
     *
     * @param user 用户实体对象
     * @param clientIP 客户端IP
     */
    private void processAfterLogin(User user, String clientIP) {
        // 更新最后活跃时间
        user.setUpdatedAt(LocalDateTime.now());
        userMapper.updateById(user);

        // 更新设备指纹（记录最新登录设备）
        updateDeviceFingerprint(user.getUsername());
    }

    /**
     * 构建登录响应VO - 数据组装和脱敏
     * 将用户实体转换为前端可用的视图对象，并进行敏感信息脱敏
     *
     * @param user 用户实体对象
     * @param accessToken 访问令牌
     * @param refreshToken 刷新令牌
     * @return UserVO 脱敏后的用户视图对象
     */
    // TODO refreshToken还未添加到 数据库 domain
    private UserVO buildLoginUserVO(User user, String accessToken, String refreshToken) {
        UserVO userVO = convertToUserVo(user);
        userVO.setToken(accessToken);
        userVO.setPhone(maskPhone(userVO.getPhone()));  // 手机号脱敏
        userVO.setEmail(maskEmail(userVO.getEmail()));   // 邮箱脱敏
        return userVO;
    }

    // ========== 工具类方法 ==========

    /**
     * 获取客户端真实IP - 网络层工具方法
     * 从HTTP请求中提取客户端真实IP，支持代理场景
     *
     * 获取优先级：
     * 1. X-Forwarded-For（代理转发IP）
     * 2. X-Real-IP（真实IP）
     * 3. RemoteAddr（直接连接IP）
     *
     * @return String 客户端真实IP地址
     */
    private String getClientIP() {
        try {
            HttpServletRequest request = ((ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes()).getRequest();

            // 1. 从代理头获取（nginx等反向代理）
            String ip = request.getHeader("X-Forwarded-For");
            if (StringUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
                return ip.split(",")[0].trim(); // 取第一个IP（真实客户端IP）
            }

            // 2. 从真实IP头获取
            ip = request.getHeader("X-Real-IP");
            if (StringUtils.isNotEmpty(ip) && !"unknown".equalsIgnoreCase(ip)) {
                return ip;
            }

            // 3. 直接获取远程地址（最后兜底）
            return request.getRemoteAddr();
        } catch (Exception e) {
            logger.warn("获取客户端IP失败", e);
            return "unknown"; // 异常情况返回未知标识
        }
    }

    /**
     * 生成设备指纹 - 客户端识别
     * 基于HTTP请求头生成设备唯一标识，用于设备识别和审计
     *
     * 指纹组成：
     * - User-Agent：浏览器/设备信息
     * - Accept：接受的内容类型
     * - Accept-Language：语言偏好
     * - Accept-Encoding：编码方式
     *
     * @return String 设备指纹哈希值
     */
    private String getDeviceFingerprint() {
        try {
            HttpServletRequest request = ((ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes()).getRequest();

            String userAgent = request.getHeader("User-Agent"); // 浏览器
            String accept = request.getHeader("Accept"); // 接受的内容类型
            String acceptLanguage = request.getHeader("Accept-Language"); // 语言偏好
            String acceptEncoding = request.getHeader("Accept-Encoding"); // 编码方式

            // 组合设备特征并生成哈希指纹
            String fingerprint = userAgent + "|" + accept + "|" + acceptLanguage + "|" + acceptEncoding;
            return String.valueOf(fingerprint.hashCode());
        } catch (Exception e) {
            logger.warn("获取设备指纹失败", e);
            return "unknown_device"; // 异常情况返回未知设备标识
        }
    }

    /**
     * 记录登录指标 - 监控和统计
     * 记录登录相关的监控指标，用于业务分析和系统监控
     *
     * @param username 用户名
     * @param clientIP 客户端IP
     */
    private void recordLoginMetrics(String username, String clientIP) {
        String metricsKey = "login_metrics:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
        redisUtil.increment(metricsKey, 1);
        redisUtil.expire(metricsKey, 1, TimeUnit.HOURS);
    }

    // ========== 参数校验方法 ==========

    /**
     * 登录参数校验 - 基础格式验证
     * 验证登录请求参数的完整性和格式正确性
     *
     * @param loginDTO 登录请求参数
     * @throws ValidationException 参数校验失败时抛出异常
     */
    private void validateLoginParams(LoginDTO loginDTO) {
        String username = loginDTO.getUsername();
        String password = loginDTO.getPassword();

        if (StringUtils.isEmpty(username)) {
            throw ValidationException.of(ValidationErrorCode.USERNAME_EMPTY);
        }

        if (StringUtils.isEmpty(password)) {
            throw ValidationException.of(ValidationErrorCode.PASSWORD_EMPTY);
        }

        if (password.length() < 6 || password.length() > 20) {
            throw ValidationException.of(ValidationErrorCode.PASSWORD_LENGTH_INVALID);
        }

        checkPasswordStrength(password);
    }

    /**
     * 检查密码强度 - 安全策略
     * 检查密码是否属于常见弱密码，提升账户安全性
     *
     * @param password 待检查的密码
     * @throws ValidationException 密码强度不足时抛出异常
     */
    private void checkPasswordStrength(String password) {
        Set<String> weakPasswords = Set.of("123456", "password", "admin", "111111");
        if (weakPasswords.contains(password.toLowerCase())) {
            throw ValidationException.of(ValidationErrorCode.PARAM_INVALID, "密码强度不足");
        }
    }

    // ========== 数据转换方法 ==========

    /**
     * 转换为用户VO对象 - 数据脱敏转换
     * 将数据库实体对象转换为前端可用的视图对象
     *
     * @param user 用户实体对象
     * @return UserVO 视图对象
     */
    private UserVO convertToUserVo(User user) {
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    /**
     * 注册后更新缓存 - 缓存一致性
     * 用户注册成功后更新相关缓存，确保数据一致性
     *
     * @param newUser 新注册的用户对象
     */
    private void updateUserCacheAfterRegister(User newUser) {
        // 缓存用户基本信息
        cacheUserInfo(newUser);

        // 缓存用户存在性标记（用于防重复注册检查）
        LoginDTO tempDTO = new LoginDTO();
        tempDTO.setUsername(newUser.getUsername());
        tempDTO.setEmail(newUser.getEmail());
        tempDTO.setPhone(newUser.getPhone());
        cacheUserExistsInfo(tempDTO);
    }

    /**
     * 创建新用户 - 数据组装
     * 根据注册请求创建用户实体对象，设置默认值和加密密码
     *
     * @param loginDTO 注册请求参数
     * @return User 用户实体对象
     */
    private User createNewUser(LoginDTO loginDTO) {
        User user = new User();
        user.setUsername(loginDTO.getUsername());
        user.setPassword(passwordEncoder.encode(loginDTO.getPassword())); // 密码加密
        user.setEmail(loginDTO.getEmail());
        user.setPhone(loginDTO.getPhone());
        user.setRealName(loginDTO.getRealName());
        user.setRole(UserRole.CUSTOMER);           // 默认用户角色
        user.setRoleStatus(UserRoleStatus.PENDING); // 默认待审核状态
        user.setAccountStatus(AccountStatus.ENABLED); // 默认启用状态
        user.setCreatedAt(LocalDateTime.now());
        user.setUpdatedAt(LocalDateTime.now());
        return user;
    }

    /**
     * 检查用户是否存在 - 重复注册验证
     * 多维度检查用户是否已存在（用户名、邮箱、手机号）
     *
     * @param loginDTO 注册请求参数
     * @return boolean 用户是否存在
     */
    private boolean checkUserExists(LoginDTO loginDTO) {
        return checkUserExistsInRedis(loginDTO) || checkUserExistsInDatabase(loginDTO);
    }

    /**
     * 缓存用户存在信息 - 性能优化
     * 将用户存在性信息缓存到Redis，提升重复检查性能
     *
     * @param loginDTO 包含用户标识信息的DTO
     */
    private void cacheUserExistsInfo(LoginDTO loginDTO) {
        redisUtil.set("user_exists:username:" + loginDTO.getUsername(), "1", 10, TimeUnit.MINUTES);
        redisUtil.set("user_exists:email:" + loginDTO.getEmail(), "1", 10, TimeUnit.MINUTES);
        redisUtil.set("user_exists:phone:" + loginDTO.getPhone(), "1", 10, TimeUnit.MINUTES);
    }

    /**
     * 检查缓存中用户是否存在 - 快速检查
     * 从Redis缓存检查用户存在性（高性能）
     *
     * @param loginDTO 注册请求参数
     * @return boolean 缓存中是否存在用户
     */
    private boolean checkUserExistsInRedis(LoginDTO loginDTO) {
        String usernameKey = "user_exists:username:" + loginDTO.getUsername();
        String emailKey = "user_exists:email:" + loginDTO.getEmail();
        String phoneKey = "user_exists:phone:" + loginDTO.getPhone();

        return Boolean.TRUE.equals(redisUtil.hasKey(usernameKey)) ||
                Boolean.TRUE.equals(redisUtil.hasKey(emailKey)) ||
                Boolean.TRUE.equals(redisUtil.hasKey(phoneKey));
    }

    /**
     * 检查数据库中用户是否存在 - 准确检查
     * 从数据库检查用户存在性（准确性强）
     *
     * @param loginDTO 注册请求参数
     * @return boolean 数据库中是否存在用户
     */
    private boolean checkUserExistsInDatabase(LoginDTO loginDTO) {
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        return userMapper.exists(queryWrapper.eq(User::getUsername, loginDTO.getUsername())) ||
                userMapper.exists(queryWrapper.eq(User::getEmail, loginDTO.getEmail())) ||
                userMapper.exists(queryWrapper.eq(User::getPhone, loginDTO.getPhone()));
    }

    /**
     * 注册参数校验 - 完整性验证
     * 验证注册请求所有必需参数的完整性和正确性
     *
     * @param loginDTO 注册请求参数
     * @throws ValidationException 参数校验失败时抛出异常
     */
    private void validateRegisterParams(LoginDTO loginDTO) {
        String username = loginDTO.getUsername();
        String password = loginDTO.getPassword();
        String email = loginDTO.getEmail();
        String phone = loginDTO.getPhone();
        String realName = loginDTO.getRealName();

        if (StringUtils.isEmpty(username)) {
            throw ValidationException.of(ValidationErrorCode.USERNAME_EMPTY);
        }

        if (StringUtils.isEmpty(password)) {
            throw ValidationException.of(ValidationErrorCode.PASSWORD_EMPTY);
        }

        if (StringUtils.isEmpty(email)) {
            throw ValidationException.of(ValidationErrorCode.EMAIL_EMPTY);
        }

        if (StringUtils.isEmpty(phone)) {
            throw ValidationException.of(ValidationErrorCode.PHONE_EMPTY);
        }

        if (StringUtils.isEmpty(realName)) {
            throw ValidationException.of(ValidationErrorCode.REALNAME_EMPTY);
        }
    }

    // ========== 数据脱敏方法 ==========

    /**
     * 手机号脱敏 - 隐私保护
     * 将手机号中间4位替换为*号，保护用户隐私
     *
     * @param phone 原始手机号
     * @return String 脱敏后的手机号
     */
    private String maskPhone(String phone) {
        if (StringUtils.isBlank(phone) || phone.length() != 11) {
            return phone; // 格式不正确时返回原值
        }
        return phone.substring(0, 3) + "****" + phone.substring(7);
    }

    /**
     * 邮箱脱敏 - 隐私保护
     * 将邮箱前缀部分字符替换为*号，保护用户隐私
     *
     * @param email 原始邮箱
     * @return String 脱敏后的邮箱
     */
    private String maskEmail(String email) {
        if (StringUtils.isBlank(email) || !email.contains("@")) {
            return email; // 格式不正确时返回原值
        }
        int atIndex = email.indexOf("@");
        String prefix = email.substring(0, atIndex);
        if (prefix.length() <= 2) {
            return prefix.charAt(0) + "***" + email.substring(atIndex);
        }
        return prefix.substring(0, 2) + "***" + email.substring(atIndex);
    }
}